package com.codeanalysissys.backend.golang;

import com.codeanalysissys.backend.golang.GoClass;
import com.codeanalysissys.backend.golang.GoMethod;
import com.codeanalysissys.backend.golang.parser.GoLexer;
import com.codeanalysissys.backend.golang.parser.GoParser;
import com.codeanalysissys.backend.golang.parser.GoParserBaseVisitor;
import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.TokenSource;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.tree.ParseTree;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class GoClassExtractor {

    public static List<GoClass> extractInfo(String goFile) throws IOException {
        return extractInfo(new File(goFile));
    }

    public static List<GoClass> extractInfo(File goFile) throws IOException {
        return extractInfo(goFile.getAbsolutePath(), new FileInputStream(goFile));
    }

    public static List<GoClass> extractInfo(String goFilePath, InputStream inStream) throws IOException {
        ANTLRInputStream input = new ANTLRInputStream(inStream);
        GoLexer lexer = new GoLexer((CharStream) input);
        CommonTokenStream tokens = new CommonTokenStream((TokenSource) lexer);
        GoParser parser = new GoParser((TokenStream) tokens);
        ParseTree tree = parser.sourceFile();
        return extractInfo(goFilePath, tree);
    }

    public static List<GoClass> extractInfo(ParseTree tree) {
        GoClassVisitor visitor = new GoClassVisitor();
        return visitor.build(tree);
    }

    public static List<GoClass> extractInfo(String GoFilePath, ParseTree tree) {
        GoClassVisitor visitor = new GoClassVisitor(GoFilePath);
        return visitor.build(tree);
    }


    private static ZipEntry[] getPackageEntries(ZipFile zip, String qualifiedName) {
        // qualifiedName ends with ".*"
        String pkg = qualifiedName.replace('.', '/').substring(0, qualifiedName.length() - 1);
        int slashCount = countSlashes(pkg);
        ArrayList<ZipEntry> entries = new ArrayList<>();
        Enumeration<? extends ZipEntry> zipEntries = zip.entries();
        while (zipEntries.hasMoreElements()) {
            ZipEntry entry = zipEntries.nextElement();
            if (entry.getName().startsWith(pkg)
                    && !entry.isDirectory()
                    && slashCount == countSlashes(entry.getName())) {
                entries.add(entry);
            }
        }
        return entries.toArray(new ZipEntry[entries.size()]);
    }

    private static int countSlashes(String str) {
        int slashCount = 0;
        for (char chr: str.toCharArray())
            if (chr == '/')
                ++slashCount;
        return slashCount;
    }

    private static class GoClassVisitor extends GoParserBaseVisitor<String> {

        private String filePath;
        private boolean isStatic;
        private boolean isAbstract;
        private String packageName;
        private String lastModifier;
        private List<String> importsList;
        private List<GoClass> GoClasses;
        private Deque<GoClass> activeClasses;

        public GoClassVisitor(String path) {
            filePath = path;
        }

        public GoClassVisitor() {
            filePath = null;
        }

        public List<GoClass> build(ParseTree tree) {
            packageName = "fmt";
            isStatic = false;
            isAbstract = false;
            GoClasses = new ArrayList<>();
            importsList = new ArrayList<>();
            activeClasses = new ArrayDeque<>();
            String[] imports = new String[1];
            imports[0] = "null";
            GoClass cls = new GoClass("main", packageName, "null", filePath, imports);
            activeClasses.push(cls);
            visit(tree);
            GoClasses.add(activeClasses.pop());
            return GoClasses;
        }

        @Override
        public String visitPackageClause(GoParser.PackageClauseContext ctx) {
            // packageDeclaration :  annotation* 'package' qualifiedName ';'
            if (ctx != null && ctx.IDENTIFIER() != null)
                packageName = ctx.IDENTIFIER().getText();
            return null;
        }

        @Override
        public String visitImportDecl(GoParser.ImportDeclContext ctx) {
            // importDeclaration :  'import' 'static'? qualifiedName ('.' '*')? ';'
            String qualifiedName = ctx.importSpec(0).getText();
            int last = ctx.getChildCount() - 1;
            if (ctx.getChild(last - 1).getText().equals("*")
                    && ctx.getChild(last - 2).getText().equals("."))
                qualifiedName += ".*";
            importsList.add(qualifiedName);
            return null;
        }

        @Override
        public String visitFunctionDecl(GoParser.FunctionDeclContext ctx) {
            // methodDeclaration
            //   :  (typeType|'void') Identifier formalParameters ('[' ']')*
            //      ('throws' qualifiedNameList)? ( methodBody | ';' )
            //
            // formalParameters
            //   :  '(' formalParameterList? ')'
            //
            // formalParameterList
            //   :  formalParameter (',' formalParameter)* (',' lastFormalParameter)?
            //   |  lastFormalParameter
            //
            // formalParameter
            //   :  variableModifier* typeType variableDeclaratorId
            //
            // lastFormalParameter
            //   :  variableModifier* typeType '...' variableDeclaratorId
            //
            // variableDeclaratorId
            //   :  Identifier ('[' ']')*
            //
            String type = "func";
            String name = ctx.IDENTIFIER().getText();
            String[] args = null;
            List<String> argsList = new ArrayList<>();
            if (ctx.signature() != null) {
                for (GoParser.ParametersContext param :
                        ctx.signature().parameters(0)) {
                    if(param.parameterDecl(0)!=null) {
                        argsList.add(visit(param.parameterDecl(0).type_()));
                    }
                    else {
                        argsList.add("Empty");
                    }
                }
            }
            if (argsList.size() > 0)
                args = argsList.toArray(new String[argsList.size()]);
            int line = ctx.getStart().getLine();
            if(lastModifier==null)
            {
                lastModifier="Empty";
            }
            if (activeClasses.peek() != null) {
                activeClasses.peek().addMethod(new GoMethod(lastModifier, isStatic, isAbstract, type, name, args, line));
            }
            return null;
        }


    }
}

